package com.oriole.admin.config.local;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.oriole.admin.config.TokenProvider;
import com.oriole.common.constant.Constants;
import com.oriole.common.constant.ResultModel;
import com.oriole.common.constant.ResultStatus;
import com.oriole.common.util.OrioleStringUtils;
import com.oriole.entity.security.OrioleUserDetails;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.PrintWriter;

/**
 * Created by IntelliJ IDEA.
 *
 * @author doublelife
 * Date: 2020/8/20 22:57
 * description:
 */
@Configuration
@EnableWebSecurity
@ConditionalOnProperty(prefix = "oriole", name = "login-type", havingValue = "local")
public class LocalWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

    private static final Logger log = LoggerFactory.getLogger(LocalWebSecurityConfigurerAdapter.class);

    @Resource
    private LocalUserDetailsServiceImpl userDetailsService;

    @Resource
    private ObjectMapper objectMapper;

    @Resource
    private OrioleJwtGenericFilterBean genericFilterBean;

    @Resource
    private SessionRegistry sessionRegistry;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.httpBasic()
                // 未登录时，进行json格式的提示，很喜欢这种写法，不用单独写一个又一个的类
                .authenticationEntryPoint((request, response, authException) -> {
                    response.setContentType(Constants.CONTENT_TYPE);
                    PrintWriter writer = response.getWriter();
                    writer.write(objectMapper.writeValueAsString(new ResultModel<>(HttpStatus.FORBIDDEN, "no login!")));
                    writer.flush();
                    writer.close();
                })
                .and().authorizeRequests()
                .antMatchers("/actuator/**", "/hystrix/**", "/webjars/**")
                .permitAll()
                //必须授权才能范围
                .and().authorizeRequests().anyRequest().authenticated()
                //使用自带的登录
                .and()
                .addFilterBefore((request, response, chain) -> {
                    HttpServletRequest req = (HttpServletRequest) request;
                    if ("POST".equals(req.getMethod()) && "/login".equals(req.getRequestURI())) {
                        Object verifyCode = req.getSession().getAttribute(Constants.SESSION_KEY_VERIFYCODE);
                        if (OrioleStringUtils.isNull(verifyCode) || StringUtils.isBlank(verifyCode.toString()) || !verifyCode.toString().equalsIgnoreCase(request.getParameter("captcha"))) {
                            response.setContentType(Constants.CONTENT_TYPE);
                            PrintWriter writer = response.getWriter();
                            writer.write(objectMapper.writeValueAsString(new ResultModel<>(ResultStatus.FAIL.getValue(), Constants.CAPTCHA_MSG)));
                            writer.flush();
                            writer.close();
                            return;
                        }
                    }
                    chain.doFilter(request, response);
                }, UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(genericFilterBean, UsernamePasswordAuthenticationFilter.class)
                .formLogin()
                .loginProcessingUrl("/login").loginPage("/loginPage")
                .permitAll()
                //登录失败，返回json
                .failureHandler((request, response, exception) -> {
                    response.setContentType(Constants.CONTENT_TYPE);
                    PrintWriter writer = response.getWriter();
                    writer.write(objectMapper.writeValueAsString(new ResultModel<>(ResultStatus.FAIL, exception.getMessage())));
                    writer.flush();
                    writer.close();
                })
                //登录成功，返回json
                .successHandler((request, response, authentication) -> {
                    String token = new TokenProvider().createToken(authentication);
                    response.addHeader(Constants.HEADER_STRING, Constants.TOKEN_PREFIX + " " + token);
                    response.setHeader("Access-control-Expose-Headers", Constants.HEADER_STRING);
                    Object principal = authentication.getPrincipal();
                    if (principal instanceof OrioleUserDetails) {
                        OrioleUserDetails userDetails = (OrioleUserDetails) principal;
                        response.setContentType(Constants.CONTENT_TYPE);
                        PrintWriter writer = response.getWriter();
                        writer.write(objectMapper.writeValueAsString(new ResultModel<>(HttpStatus.OK.value(), "登录成功", userDetails)));
                        //校验通过，注册session
                        sessionRegistry.registerNewSession(request.getSession().getId(), token);
                        log.debug("token is {}", token);
                        writer.flush();
                        writer.close();
                    }
                })
                .and().exceptionHandling()
                // 没有权限，返回json
                .accessDeniedHandler((request, response, accessDeniedException) -> {
                    response.setContentType(Constants.CONTENT_TYPE);
                    PrintWriter writer = response.getWriter();
                    writer.write(objectMapper.writeValueAsString(new ResultModel<>(HttpStatus.UNAUTHORIZED, "Unauthorized")));
                    writer.flush();
                    writer.close();
                })
                .and().logout()
                //退出成功，返回json
                .logoutSuccessHandler((request, response, authentication) -> {
                    response.setContentType(Constants.CONTENT_TYPE);
                    PrintWriter writer = response.getWriter();
                    writer.write(objectMapper.writeValueAsString(new ResultModel<>(HttpStatus.OK.value(), "退出成功", "logout")));
                    String sessionId = request.getRequestedSessionId();
                    SessionInformation sessionInformation = sessionRegistry.getSessionInformation(sessionId);
                    if (sessionInformation != null) {
                        sessionRegistry.removeSessionInformation(sessionId);
                    }
                    writer.flush();
                    writer.close();
                }).permitAll();
        //开启跨域访问
        http.cors().disable();
        //开启模拟请求，比如API POST测试工具的测试，不开启时，API POST为报403错误
        http.csrf().disable();
        // iframe
        http.headers().frameOptions().disable();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/common/**")
                .antMatchers("/getVerifyCodeImage")
                .antMatchers(HttpMethod.OPTIONS, "/**");
        super.configure(web);
    }
}
